package net.xiake6.orm.datasource;

import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.annotation.PostConstruct;
import javax.annotation.Resource;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Service;

import lombok.extern.slf4j.Slf4j;

/**
 * 执行mapper方法之前的切面 <br>
 * 获取datasource对象之前往HandleDataSource中指定当前线程数据源路由的key <br>
 * ClassName:DataSourceAspect <br/>
 * Date: 2017年8月16日 上午9:09:34 <br/>
 * 
 * @author fenglibin
 * @version
 * @see
 */
@Slf4j
@Service
public class DataSourceAspect {

	@Resource(name = "targetDataSources")
	private Map<String, javax.sql.DataSource> targetDataSources;
	/**
	 * 按主从分后的数据源
	 */
	private Map<DataSourceType, List<String>> dataSources = new HashMap<DataSourceType, List<String>>();

	/**
	 * 拆分主从数据源
	 * 
	 * @param targetDataSources
	 */
	@PostConstruct
	public void setTargetDataSources() {
		dataSources.put(DataSourceType.MASTER, new ArrayList<String>());
		dataSources.put(DataSourceType.SLAVE, new ArrayList<String>());
		if (targetDataSources != null && targetDataSources.size() > 0) {

			for (Map.Entry<String, javax.sql.DataSource> dataSource : targetDataSources.entrySet()) {
				String name = dataSource.getKey();
				if (name.startsWith(DataSourceType.MASTER.toString())) {
					dataSources.get(DataSourceType.MASTER).add(name);
				} else if (name.startsWith(DataSourceType.SLAVE.toString())) {
					dataSources.get(DataSourceType.SLAVE).add(name);
				}
			}
		}
	}

	/**
	 * 在mapper层方法之前获取datasource对象之前在切面中指定当前线程数据源路由的key
	 */
	public void before(JoinPoint point) {

		Object target = point.getTarget();
		log.debug("target:" + target.toString());
		String method = point.getSignature().getName();
		log.debug("method:" + method);
		Class<?>[] classz = target.getClass().getInterfaces();
		Class<?>[] parameterTypes = ((MethodSignature) point.getSignature()).getMethod().getParameterTypes();
		try {
			Method m = classz[0].getMethod(method, parameterTypes);
			if (m != null) {
				String methodName = m.getName();
				log.debug("method name:" + methodName);
				/**
				 * 先判断方法本身是否有执行数据源注解，如果有则依据注解进行处理，如果没有则根据方法的类型获取数据源
				 */
				if (m.isAnnotationPresent(DataSource.class)) {
					DataSource dataSource = m.getAnnotation(DataSource.class);
					log.debug("Batabase type ：" + dataSource.value());
					HandleDataSource.putDataSource(getDataSourceName(dataSource.value()));
				} else if (m.isAnnotationPresent(DataSourceMaster.class)) {
					log.debug("DataSourceMaster annotation found. Set database type to master");
					HandleDataSource.putDataSource(getDataSourceName(DataSourceType.MASTER));
				} else if (m.isAnnotationPresent(DataSourceSlave.class)) {
					log.debug("DataSourceSlave annotation found. Set database type to slave");
					HandleDataSource.putDataSource(getDataSourceName(DataSourceType.SLAVE));
				} else {// 没有指定主从操作的数据库操作方法
					if (methodName.startsWith(DataOperateType.DELETE.getName())
							|| methodName.startsWith(DataOperateType.UPDATE.getName())
							|| methodName.startsWith(DataOperateType.INSERT.getName())) {// 增删改操作路由到主库
						log.debug("Delete, update or insert operation found. Set database type to master");
						HandleDataSource.putDataSource(getDataSourceName(DataSourceType.MASTER));
					} else if (methodName.startsWith(DataOperateType.SELECT.getName())
							|| methodName.startsWith(DataOperateType.GET.getName())
							|| methodName.startsWith(DataOperateType.QUERY.getName())) {
						log.debug("Select, get or query operation found. Set database type to slave");
						String dataSourceName = getDataSourceName(DataSourceType.SLAVE);
						if (dataSourceName == null || dataSourceName.trim().length() == 0) {
							// 如果没有获取到从库，则从主库中获取
							dataSourceName = getDataSourceName(DataSourceType.MASTER);
						}
						HandleDataSource.putDataSource(dataSourceName);
					}
				}
			}
		} catch (Exception e) {
			log.error("Aspect datasource error happened:" + e.getMessage(), e);
		}
	}

	/**
	 * 获取用户的指定，获取对应的master或slave数据源。<br>
	 * 如果单个种类的数据源指定了多个（如多master或多slave），会根据指定的数据源类型，任意选择一个数据源。 <br>
	 * 如指定的数据源为slave，则会在多个slave中随机选择一个；同理多master的情况下也会在数据源指定为master的情况下随机返回一个master数据源。
	 * 
	 * @param dataSource
	 * @return
	 */
	private String getDataSourceName(DataSourceType dataSourceType) {
		List<String> result = dataSources.get(dataSourceType);
		if (result != null && result.size() > 0) {
			int random = (int) (result.size() * Math.random());
			return result.get(random);
		}
		return null;
	}

}
